Makefile教程1 快速入门

1 快速入门

1.1 为什么存在 Makefile?

Makefile用于帮助决定大型程序的哪些部分需要重新编译。在绝大多数情况下,都会编译C或C++文件。 其他语言通常有自己的工具,其用途与Make类似。当您需要根据已更改的文件运行一系列指令时,Make也可以在编译之外使用。 本教程将重点介绍C/C++编译。

下面是您可以使用Make构建的示例依赖关系图。如果任何文件的依赖项发生更改,则该文件将被重新编译:

1.2 Make有哪些替代?

流行的C/C++替代构建系统有SCons、CMake、Bazel 和 Ninja。 一些代码编辑器(例如 Microsoft Visual Studio)有自己的内置构建工具。 对于Java,有Ant、Maven和Gradle。 其他语言(例如 Go、Rust 和 TypeScript)都有自己的构建工具。

Python、Ruby 和原始 avascript等解释性语言不需要与Makefile之类的东西。 Makefile的目标是根据已更改的文件来编译需要编译的任何文件。 解释语言中的文件发生更改时,不需要重新编译任何内容。 程序运行时,将使用该文件的最新版本。

1.3 Make的版本和类型

Make有多种实现,但本指南的大部分内容都适用于您使用的任何版本。 然而,它是专门为GNU Make编写的,GNU Make是Linux和MacOS上的标准实现。 所有示例都适用于Make版本3和4,除了一些深奥的差异之外,它们几乎相同。

1.4 运行示例

要运行这些示例,您需要一个终端并安装“make”。 对于每个示例,将内容放入名为Makefile的文件中,然后在该目录中运行命令make。 让我们从最简单的Makefile开始:

hello:
echo "Hello, World"

注意:Makefile必须使用TAB缩进,不能使用空格,否则make将失败。

以下是运行上述示例的输出:

$ make
echo "Hello, World"
Hello, World

1.5 Makefile语法

生成文件语法
Makefile 由一组规则组成。 规则通常如下所示:

targets: prerequisites
command
command
command

1.6 Make的本质

让我们从hello world示例开始:

hello:
echo "Hello, World"
echo "This line will print if the file hello does not exist."

然后我们将运行 make hello。 只要hello文件不存在,命令就会运行。 如果hello存在,则不会运行任何命令。

让我们创建更典型的Makefile:编译单个C文件。

blah.c

int main() { return 0; }

然后创建 Makefile(一如既往地称为 Makefile):

blah:
cc blah.c -o blah

运行make,由于没有将目标作为参数提供给make命令,因此将运行第一个目标。 在这种情况下,只有一个目标(blah)。 第一次运行它时,将会创建blah。 第二次,你会看到“make: 'blah' is up to date”。 那是因为blah文件已经存在。 但有一个问题:如果我们修改blah.c 然后运行make,则不会重新编译任何内容。

我们通过添加先决条件来解决这个问题:

blah: blah.c
cc blah.c -o blah

当我们再次运行make 时,会发生以下步骤:

最后一步很关键,也是make的精髓。它试图做的是确定自上次编译blah以来blah的先决条件是否发生了变化。也就是说,如果blah.c被修改,运行make应该重新编译该文件。 相反,如果blah.c没有更改,则不应重新编译它。

为了实现这一点,它使用文件系统时间戳来确定是否发生了更改。 这是一个合理的启发式方法,因为文件时间戳通常仅在文件被修改时才会更改。 但情况并非总是如此。 例如,您可以修改文件,然后将该文件的修改时间戳更改为旧的时间戳。 如果这样做,Make会错误地猜测该文件没有更改,因此可以被忽略。

唷,真是拗口啊。 确保您理解这一点。 这是 Makefile 的关键,您可能需要几分钟才能正确理解。 如果事情仍然令人困惑,请尝试上面的示例或观看上面的视频。

参考资料

1.6 更多示例

以下Makefile最终运行所有三个目标。 当您在终端中运行make时,它将通过一系列步骤构建名为blah的程序:

blah: blah.o
cc blah.o -o blah # Runs third

blah.o: blah.c
cc -c blah.c -o blah.o # Runs second

# Typically blah.c would already exist, but I want to limit any additional required files
blah.c:
echo "int main() { return 0; }" > blah.c # Runs first

如果删除blah.c,所有三个目标都将重新运行。如果您运行touch blah.o (从而将时间戳更改为比 blah 更新),则只有第一个目标会运行。如果您不进行任何更改,则所有目标都不会运行。

下一个示例没有做任何新内容,但仍然很好的补充示例。它将始终运行两个目标,因为some_file依赖于other_file,而other_file从未创建。

some_file: other_file
echo "This will always run, and runs second"
touch some_file

other_file:
echo "This will always run, and runs first"

1.7 Make clean

clean经常被用作删除其他目标输出的目标,你可以运行make和make clean来创建和删除some_file。

clean在这里做了两件新事情:

some_file: 
touch some_file

clean:
rm -f some_file

1.8 变量

变量只能是字符串。通常要使用 :=,但 = 也可以。

下面是一个使用变量的示例:

files := file1 file2
some_file: $(files)
echo "Look at this variable: " $(files)
touch some_file

file1:
touch file1
file2:
touch file2

clean:
rm -f file1 file2 some_file

单引号或双引号对Make没有任何意义。它们只是分配给变量的字符。不过,引号对shell/bash很有用,在printf等命令中需要用到它们。在本例中,两个命令的行为是一样的:

a := one two # a is set to the string "one two"
b := 'one two' # Not recommended. b is set to the string "'one two'"
all:
printf '$a'
printf $b

使用 ${} 或 $() 引用变量

x := dude

all:
echo $(x)
echo ${x}

# Bad practice, but works
echo $x

返回:Makefile教程1 快速入门

本文由“公众号文章抓取器”生成,请忽略上文所有联系方式或指引式信息。有问题可以联系:五人工作室,官网:www.Wuren.Work,QQ微信同号1976.424.585